Executor 框架 - 概念

概述

Executor 框架是 Java 5 中引入的,位于 java.util.concurrent 包下,其内部使用了线程池机制,可用于启动、调度和管理多个线程。通过 Executor 来启动线程比使用 Thread 的 start 方法的好处不仅在于更易于管理,效率更好,还在于有助于避免 this 逃逸问题。

this 逃逸问题是指在构造函数返回之前其他线程就持有该对象的引用。调用尚未构造完全的对象的方法可能会引发令人疑惑的错误。this 逃逸经常发生在构造函数中启动线程或注册监听器时。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ThisEscape {  
public ThisEscape() {
new Thread(new EscapeRunnable()).start();
// ...
}

private class EscapeRunnable implements Runnable {
@Override
public void run() {
// 通过ThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸
}
}
}

组成部分

Executor 框架包括有以下组件:

  • 任务:包含被执行任务需要实现的接口:Runnable 接口和 Callable 接口。
  • 任务的执行:包括任务任务机制的核心接口 Executor,以及继承自Executor 接口的 ExecutorService 接口与 CompletionService 接口。
  • 异步计算的结果:包括接口 Future,以及实现 Future 接口的 FutureTask 类。

成员介绍

Executor

Executor是一个Executor 框架的核心接口,它内部只定义了一个方法void execute(Runnable command),该方法接受一个 Runnable 实例,并将其执行。

ExecutorService

ExecutorService接口继承自 Executor 接口,它提供了更加丰富的管理多线程的方法,比如,ExecutorService 接口提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。

ExecutorService 的生命周期包括三种状态:运行、关闭、终止。

  • 运行:当实现 ExecutorService 接口的类的实例被创建后,便进入运行状态。
  • 关闭:当调用了 ExecutorService 接口内部提供的 shutdown 方法时,便平滑地进入关闭状态。平滑过渡是指在关闭状态中,ExecutorService 会停止接收任何新的任务,并且会等待所有已经提交的任务执行完成(已经提交的任务分为两类:一类是已经在执行的,另一类是还没有开始执行的)。
  • 终止:在所有已提交的任务执行完毕后,便进入了终止状态。

Executors

Executors 提供了一系列工厂方法用于用于创建功能不同的线程池,所有返回的线程池都实现了ExecutorService 接口。以下为四种常见的线程池类型:

1
2
3
4
5
6
7
8
9
10
11
// 创建固定数目线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads)

// 创建一个可缓存的线程池,调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程不可用,则创建一个新线程并添加到线程池中。终止并从缓存中移除那些已有60秒未被使用的线程
public static ExecutorService newCachedThreadPool()

// 创建一个单线程化的线程池
public static ExecutorService newSingleThreadExecutor();

// 创建一个支持定时以及周期性的任务执行的线程池,多数情况可代替 Timer 类
public static ExecutorService newScheduledThreadPool(int corePoolSize);

Future/FutureTask/Callable/Runnable

在 JDK5 之后,任务可分为两类:一类是实现了 Runnable 接口的类,另一类是实现了 Callable 接口的类。两者都能够被 ExecutorService 执行,但两者区别在于,Runnable 任务没有返回值,而 Callable 任务有返回值,且能够抛出检查异常(checked exception)。

Future 接口对具体提交的任务,封装并提供了获取结果,任务取消等操作。执行结果可通过调用 get() 方法来获取,该方法会阻塞直到任务返回结果。FutureTask 则是 Future 接口的具体实现类。

Future 封装的 Runnable 任务可以调用 get() 方法,但是其返回值为 null。

CompletionService

若通过向线程池提交了若干个任务,并通过容器保存所有 FutureTask,当需要得到执行结果的时候,可以通过循环遍历 FutureTask 的方式,调用 get() 方法获取,但是如果此时 FutureTask 尚未完成,那么此时线程便会阻塞等待至任务运行结束。由于无法准确知道哪个任务将会优先执行完成,使用循环遍历的方式效率不会很高。

在 JDK5 中提供了 CompletionService,其内部通过 BlockingQueue 来管理若干线程。ExecutorCompletionService 为 CompletionService 接口的具体实现类。

  • take() :获取任务结果。获取并移除下一个已完成任务的 Future。如果任务不存在,则等待。
  • poll() : 与 take() 功能相同,不同之点在于任务不存在时,直接返回 null。

以上两种方法特性其实就是利用了 BlockingQueue 接口的特点。


参考资料

并发新特性—Executor 框架与线程池

变量可见性和volatile, this逃逸, 不可变对象, 以及安全公开–Java Concurrency In Practice C03读书笔记

Executor框架简介 - 加大装益达

java并发编程之CompletionService - miaoLoveCode